package org.apache.rocketmq.client.java.clientapis.producer;

import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.SendMessageResponse;
import apache.rocketmq.v2.SendResultEntry;
import apache.rocketmq.v2.Status;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import org.apache.rocketmq.client.java.clientapis.ClientException;
import org.apache.rocketmq.client.java.clientapis.message.MessageId;
import org.apache.rocketmq.client.java.clientapis.producer.SendReceipt;
import org.apache.rocketmq.client.java.exception.*;
import org.apache.rocketmq.client.java.message.MessageIdCodec;
import org.apache.rocketmq.client.java.route.Endpoints;
import org.apache.rocketmq.client.java.route.MessageQueueImpl;
import org.apache.rocketmq.client.java.rpc.RpcInvocation;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
 * @author：陈清风扬，个人微信号：chenqingfengyangjj。
 * @date:2025/6/16
 * @方法描述：生产者客户端发送消息给服务端之后的回执对象，客户端接收到的响应信息会被封装到该对象中，在第十一版本代码中，我们还用不到这个类的对象
 * 因为我们还没有实现服务端回复客户端响应的操作，我们还不知道服务端究竟会回复客户端什么信息，怎么回复这些信息，所以等后面我们实现了完整的发送消息功能之后，我再跟大家解释这个类的作用
 */
public class SendReceiptImpl implements SendReceipt {
    private final MessageId messageId;
    private final String transactionId;
    private final MessageQueueImpl messageQueue;
    private final long offset;

    private SendReceiptImpl(MessageId messageId, String transactionId, MessageQueueImpl messageQueue, long offset) {
        this.messageId = messageId;
        this.transactionId = transactionId;
        this.messageQueue = messageQueue;
        this.offset = offset;
    }

    @Override
    public MessageId getMessageId() {
        return messageId;
    }

    public MessageQueueImpl getMessageQueue() {
        return messageQueue;
    }

    public String getTransactionId() {
        return transactionId;
    }

    public Endpoints getEndpoints() {
        return messageQueue.getBroker().getEndpoints();
    }

    @SuppressWarnings("unused")
    public long getOffset() {
        return offset;
    }

    public static List<SendReceiptImpl> processSendMessageResponseInvocation(MessageQueueImpl mq,
                                                                             RpcInvocation<SendMessageResponse> invocation) throws ClientException {
        final String requestId = invocation.getContext().getRequestId();
        final SendMessageResponse response = invocation.getResponse();
        Status status = response.getStatus();
        List<SendReceiptImpl> sendReceipts = new ArrayList<>();
        final List<SendResultEntry> entries = response.getEntriesList();
        // Filter abnormal status.
        final Optional<Status> abnormalStatus = entries.stream()
                .map(SendResultEntry::getStatus).filter((Predicate<Status>) s -> !s.getCode().equals(Code.OK)).findFirst();
        if (abnormalStatus.isPresent()) {
            status = abnormalStatus.get();
        }
        final Code code = status.getCode();
        if (code.equals(Code.OK)) {
            for (SendResultEntry entry : entries) {
                final MessageId messageId = MessageIdCodec.getInstance().decode(entry.getMessageId());
                final String transactionId = entry.getTransactionId();
                final long offset = entry.getOffset();
                final SendReceiptImpl impl = new SendReceiptImpl(messageId, transactionId, mq, offset);
                sendReceipts.add(impl);
            }
            return sendReceipts;
        }
        final int codeNumber = code.getNumber();
        final String statusMessage = status.getMessage();
        switch (code) {
            case ILLEGAL_TOPIC:
            case ILLEGAL_MESSAGE_TAG:
            case ILLEGAL_MESSAGE_KEY:
            case ILLEGAL_MESSAGE_GROUP:
            case ILLEGAL_MESSAGE_PROPERTY_KEY:
            case ILLEGAL_MESSAGE_ID:
            case ILLEGAL_DELIVERY_TIME:
            case MESSAGE_PROPERTY_CONFLICT_WITH_TYPE:
            case MESSAGE_CORRUPTED:
            case CLIENT_ID_REQUIRED:
                throw new BadRequestException(codeNumber, requestId, statusMessage);
            case UNAUTHORIZED:
                throw new UnauthorizedException(codeNumber, requestId, statusMessage);
            case FORBIDDEN:
                throw new ForbiddenException(codeNumber, requestId, statusMessage);
            case NOT_FOUND:
            case TOPIC_NOT_FOUND:
                throw new NotFoundException(codeNumber, requestId, statusMessage);
            case PAYLOAD_TOO_LARGE:
            case MESSAGE_BODY_TOO_LARGE:
                throw new PayloadTooLargeException(codeNumber, requestId, statusMessage);
            case TOO_MANY_REQUESTS:
                throw new TooManyRequestsException(codeNumber, requestId, statusMessage);
            case REQUEST_HEADER_FIELDS_TOO_LARGE:
            case MESSAGE_PROPERTIES_TOO_LARGE:
                throw new RequestHeaderFieldsTooLargeException(codeNumber, requestId, statusMessage);
            case INTERNAL_ERROR:
            case INTERNAL_SERVER_ERROR:
            case HA_NOT_AVAILABLE:
                throw new InternalErrorException(codeNumber, requestId, statusMessage);
            case PROXY_TIMEOUT:
            case MASTER_PERSISTENCE_TIMEOUT:
            case SLAVE_PERSISTENCE_TIMEOUT:
                throw new ProxyTimeoutException(codeNumber, requestId, statusMessage);
            case UNSUPPORTED:
            default:
                throw new UnsupportedException(codeNumber, requestId, statusMessage);
        }
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("messageId", messageId)
                .toString();
    }
}
